# 2024-3-6启动开发多线程版本
# 主要增加交易频道喊话功能
# 2024-3-28启动开发高管培训功能
from DrissionPage import ChromiumPage  # 导入浏览器类
from DrissionPage._elements.chromium_element import ChromiumElement  # 导入element类型
import re, time, pysnooper, random, logging
from DrissionPage.common import Actions
from DrissionPage.errors import ElementNotFoundError
from concurrent.futures import ThreadPoolExecutor,wait,FIRST_EXCEPTION
# from threading import Lock
from DrissionPage._pages.chromium_tab import ChromiumTab
from myError.error import MessageError
logger = logging.getLogger(__name__)
logging.basicConfig(
    filename="example.log",
    encoding="utf-8",
    level=logging.DEBUG,
    format="%(threadName)s %(asctime)s %(levelname)s %(message)s",
    datefmt="%m/%d %H:%M:%S",
)
# from DrissionPage import ChromiumOptions

# co = ChromiumOptions()
# co.set_timeouts(page_load=60)
# tLock = Lock()
# page: ChromiumPage = ChromiumPage(addr_or_opts=co)
page: ChromiumPage = ChromiumPage(timeout=60)
logging.info("浏览器启动")


ac = Actions(page)
AD_url: str = "https://www.simcompanies.com/messages/"  # 新标签的地址
tab_messages: ChromiumTab = page.new_tab()  # 创建新标签-聊天频道-发广告用
tab_executives: ChromiumTab = page.new_tab()  # 创建新标签-高管培训用
logging.info("创建第二标签")
# https://curlconverter.com/  curl转换工具
# web.get("https://www.simcompanies.com/zh/landscape/")
time.sleep(5)
logging.info("请登录，进入游戏主界面")


def number_str_to_float(txt: str, roundUp: int = 1) -> float:
    """将价格str转换为float
    :txt: 要转换的字符串,
    :roundUp: 转换后要除以的值，通常为100，将价格4643变为46.43
    """
    match1: list[str] = re.findall(r"[-?0-9]+\.?", txt)
    c = ""
    match2: str = c.join(match1)
    nums: float = float(match2)
    nums = round(nums, 2)
    return nums / roundUp


class BaseMain:
    def __init__(self, url: str = "https://www.simcompanies.com/landscape/") -> None:
        self.base_url: str = url  # 主基地页面
        """主基地页面"""
        self.closed_stores: list[ChromiumElement] = []
        """空闲的店铺"""
        self.shared_warehouse: dict = {"Diesel": 0, "Petrol": 0}
        """共享仓库存量数据"""
        self.sell_hours: str = "4hr"
        """销售小时数"""

    # @pysnooper.snoop("file.log")
    def close_web_page_reminder(self):
        """
        关闭网页提示，提示内容“程序员正在抓紧解决加载缓慢问题”。
        检查时机，每次载入网页之后
        """
        logging.debug("准备开始检查")

        new_var1: list[ChromiumElement] = page.eles(
            "x:/html/body/div[1]/div/a/*/*", timeout=5
        )
        if len(new_var1) > 0:
            logging.debug("检测到网页提示")
            # page.ele("tag:body > div:nth-child(1) > div > a > svg").click()
            new_var1[0].click()
        else:
            logging.debug("未检测到网页提示")

    @pysnooper.snoop("file.log")
    def retail(self, name: str) -> None:
        """零售商店开始卖货"""
        count1: int = 0  # 计数器
        while 1:
            page_start_time: float = time.time()
            count1 += 1
            logging.debug("第{}次扫描商店状态".format(count1))
            self.get_closed_stores()
            if len(self.closed_stores) > 0:
                logging.info("点击序列0的商店开始")
                self.closed_stores[0].click()
                logging.info("点击序列0的商店结束")
                time.sleep(15)
                # 获取库存
                try:
                    new_var1 = f'x://h3[text()="{name}"]/../span'
                    text1: str = page.ele(new_var1).text
                    nums: float = number_str_to_float(text1)  # 库存DIESEL数量
                    base_one.shared_warehouse[name] = nums  # 更新共享库存数量

                except ElementNotFoundError as e:

                    logging.warning(e, ";;;获取库存环节错误")
                    continue

                if nums > 6000:  # 判断库存是否充足
                    # 输入XX小时销量，并找到合理价格

                    a1: str = page.ele(
                        'x://h3[text()="{}"]/../div'.format(name)
                    ).text  # 平均价格
                    a2: str = number_str_to_float(a1)
                    num1: float = round(float(a2) * 0.9, 1)  # 起步价
                    num2: float = round(float(a2) * 1.1, 1)  # 终止价
                    last_money: float = -9999999.9  # 上次的利润，临时变量
                    list1: list[dict] = []
                    try:
                        logging.debug("进入售价计算模块")
                        while num1 <= num2:  # 在价格区间
                            num1 = round(num1, 1)
                            # 输入销售数量 3hr
                            page.ele(
                                'x://h3[text()="{}"]/../../../../descendant::input[@placeholder="Quantity"]'.format(
                                    name
                                )
                            ).input(
                                clear=True, vals=self.sell_hours
                            )  # 设定销售小时数，以确定销售数量
                            time.sleep(0.5)
                            # 输入销售价格
                            page.ele(
                                'x://h3[text()="{}"]/../../../../descendant::input[@placeholder="Price"]'.format(
                                    name
                                )
                            ).input(vals=num1, clear=True)
                            time.sleep(0.5)

                            page.ele(
                                'x://h3[text()="{}"]/../div/*'.format(name)
                            ).click()  # 点击问号显示利润计算页面
                            # time.sleep(1)                                                        
                            new_var2 = "x://table/tbody/tr[5]/td[3]"
                            time.sleep(0.5)
                            _money = page.ele(new_var2).text
                            _money = number_str_to_float(_money)
                            page.ele(
                                'x://div[text()="Retailing breakdown"]/button'
                            ).click()  # 点击关闭利润计算页面
                            time.sleep(0.8)
                            dict2: dict = {"售价": num1, "利润总和": _money}
                            logging.debug(dict2)
                            if last_money > _money:  # 判断利润总和是否增长
                                break
                            else:
                                last_money = _money
                                list1.append(dict2)
                            num1 += 0.1  # 每次增加的单位价格
                        logging.debug("离开售价计算模块")
                    except ElementNotFoundError as e:
                        logging.warning(e, ";;;价格区间出错啦")

                        break

                    if len(list1) > 0:
                        good_price: dict = max(list1, key=lambda x: x["利润总和"])
                        logging.info(
                            "销售{a},最佳利润是 {b}".format(
                                b=good_price, a=self.sell_hours
                            ),
                        )
                        # 输入X小时的最佳销PRICE
                        page.ele(
                            'x://h3[text()="{}"]/../../../../descendant::input[@placeholder="Price"]'.format(
                                name
                            )
                        ).input(good_price["售价"], clear=True)
                        # 点击销售按钮
                        page.ele(
                            'x://h3[text()="{}"]/../../../../descendant::button[text()="Sell"]'.format(
                                name
                            )
                        ).click()
                    else:
                        break

                else:
                    # 库存不足时，先合同采购
                    # if self.buying_incoming_contracts():
                    # print("合同采购成功")

                    # else:
                    #     print("合同采购失败，进入交易所采购")
                    #     # 库存不足,且没有合同时，交易所采购
                    #     self.purchase_at_market_price()
                    logging.info("当前{}数量不足，退出内层循环".format(name))

                    break

            else:
                time.sleep(10)  # 间隔几秒扫描一次商店零售状态
                page_end_time: float = time.time()
                time2 = page_end_time - page_start_time
                if time2 > 300:  # 当在页面停留时间超过N秒后刷新页面
                    page.refresh()
                    time.sleep(15)


    # @pysnooper.snoop("file.log")
    def buying_incoming_contracts(self) -> bool:
        """从合同采购"""
        page.get(
            "https://www.simcompanies.com/headquarters/warehouse/incoming-contracts/",
            timeout=60,
        )
        time.sleep(5)
        # 合同定位，eles
        # //div[text()="Incoming contracts"]/../div[2]/div
        contracts: list[ChromiumElement] = page.eles(
            'x://div[text()="Incoming contracts"]/../div[2]/div', timeout=60
        )
        # 如果有合同
        if len(contracts) > 0:
            # TODO 创建列表 保存字典，便利每个元素加入字典，加入列表，选择价格最低且现金足够支付的合同购买
            dicts: list[dict] = []  # 存放每个合同信息
            # 当前现金数量
            _money: str = page.ele("x://nav/div/div/div[2]/div[1]/a/div/div[2]").text[
                1:
            ]
            _price: float = number_str_to_float(_money)
            # 统计所有合同信息
            for i in contracts:
                # 获取价格文本
                _pr = i.ele("x://b").text[1:]
                # 价格的数字
                _pr = number_str_to_float(_pr)
                # /div[3]//span
                # 一份合同的总价款
                sum_money: str = i.ele("x:/div[3]//span").text[1:]
                sum_money: float = number_str_to_float(sum_money)
                dict0 = {"ele": i, "price": _pr, "sum_money": sum_money}
                dicts.append(dict0)
            good_dict: dict = {"price": 789, "sum_money": 789789}
            # 判断当前最佳合同
            for d in dicts:
                # 当前现金足够支付的,价格最低的
                if d["price"] < good_dict["price"] and _price - d["sum_money"] > 0:
                    good_dict = d
            if good_dict["price"] != 789 and good_dict["price"] < 40:
                # 点击购买合同按钮
                ele2: ChromiumElement = good_dict["ele"]
                ele2.ele('x:/div[3]//a[@aria-label="Sign contract"]').click()
                print("点击购买{c1}".format(c1=good_dict))
                return True
            else:
                return False
        else:
            return False

    # @pysnooper.snoop("file.log")
    def purchase_at_market_price(self):
        """从交易所采购"""
        # i_want_buy:list[str] = ['DIESEL','汽油']  11是汽油，12是DIESEL
        page.get(
            "https://www.simcompanies.com/zh/market/resource/12/", timeout=60
        )  # 打开交易所-DIESEL
        # 输入当前金额可购买的数量
        _price: float = float(
            page.ele(
                # 商品最低价格
                "x://table/tbody/tr[1]/td[4]/div/span"
            ).text[1:]
        )
        # 如果市场价高于 40，不买，返回
        if _price >= 38:
            time.sleep(2)
            return
        _money: str = page.ele(
            # 现金数量
            "x://nav/div/div/div[2]/div[1]/a/div/div[2]"
        ).text[1:]
        _c: float = number_str_to_float(_money)
        _buy_nums: float = _c / _price
        if _buy_nums >= 1000:
            _buy_nums = 1000
        page.ele('x://button[text()="Buy"]/../../descendant::input').input(
            vals=int(_buy_nums), clear=True
        )
        time.sleep(1)
        page.ele('x://button[text()="Buy"]').click()

    # @pysnooper.snoop("file.log")
    def get_closed_stores(self):
        """获取基地内所有空闲商店"""
        wait_eles_1: str = 'x://*[@id="page"]/div/div/div[2]/div/a[5]/img[1]'

        if ("landscape" not in page.url) or (
            page.wait.eles_loaded(wait_eles_1, timeout=30) == False
        ):
            page.get(self.base_url,timeout=65)  # 打开主页面
            self.close_web_page_reminder()
        else:
            self.close_web_page_reminder()

        # 找到所有加油站

        petrol_stations: list[ChromiumElement] = page.eles(
            'x://span[text()="Gas station"]/../..', timeout=2
        )
        # 重置空闲加油站为0个
        self.closed_stores = []
        for i in petrol_stations:
            _a: list = i.eles('x:descendant::span[text()="Selling"]', timeout=0.2)
            _b: list = i.eles(
                'x://span[text()="Gas station"]/../..//b[text()="Rush for "]',
                timeout=0.2,
            )
            # //span[text()="Gas station"]/../..//b[text()="Rush for "]  升级状态
            if not (
                len(_a) > 0 or len(_b) > 0
            ):  # 如果没有'正在零售Selling'字样就加list
                self.closed_stores.append(i)
        if len(self.closed_stores) > 0:
            today = time.ctime()
            # print("空闲的商店有{}个，请注意,{}".format(len(self.closed_stores), today))
            logging.info("空闲的商店有{}个，请注意".format(len(self.closed_stores)))

    @pysnooper.snoop("file.log")
    def cash_vs_in_comings(self):
        """现金与进货合同金额的对比，现金是否大于进货合同金额，如果是返回True，否则返回False"""
        # 打开进货合同页面
        tab_messages.ele('x://span[text()="Warehouse"]', timeout=60).click()
        tab_messages.wait.load_start()
        # time.sleep(15)
        # 定位进货合同金额
        a2: str = 'x://span[text()="Incoming"]/../div/div'
        if (in_comings := tab_messages.ele(a2, timeout=60).text) and len(in_comings) > 0:
            if "m" in in_comings or "M" in in_comings:
                print(in_comings)
                in_comings = in_comings.split("(")[0]
                in_comings = number_str_to_float(in_comings, 1) * 1_000_000

            elif "k" in in_comings or "K" in in_comings:
                print(in_comings)
                in_comings = number_str_to_float(in_comings, 1) * 1_000
        else:
            return True
            

        cash_xpath = (
            'x://*[@id="root"]/div/div[1]/nav/div/div/div[2]/div[1]/a/div/div[2]'
        )
        cash = tab_messages.ele(cash_xpath, timeout=60).text
        cash = number_str_to_float(cash)
        print("进货合同金额：", in_comings)  # 进货合同金额
        print("现金金额：", cash)  # 现金金额
        if cash > in_comings:
            return True
        else:
            return False

 
    def place_AD(self):
        """发布广告\n\n具体行为定义"""
        AD_price: str = """
the price is MP-3%
        """  # 发布广告-内嵌价格部分
        # 下面这段英文是采购升级建筑材料的
        # Buying :re-111: 100  @MP-2%
        AD_str: str = (
            """
Buying :re-12: -|---- :re-11: 
{}
-----------------

Please send me the contract directly,The quantity of each contract is not higher than 10K,I will buy it in 8 hours.I will give priority to purchasing products with a lower price.
            """.format(
                AD_price
            )
        )
        AD_str_ZH: str = (
            """
收购 :re-12: --|----- :re-11:  
{}
请直接发合同，每个合同请少于10K数量，我经常在线，但最迟8小时内购买.我会先买价格更低的.
            """.format(
                AD_price
            )
        )

        messages_EN: str = 'x://a[@href="/messages/chatroom_Sales/"]'
        messages_ZH: str = 'x://a[@href="/messages/chatroom_[ZH] 交易-chatroom_Sales/"]'
        enter_xpath = "x://textarea/../../../../../../../../../../div[1]//textarea/../../..//button"  # 回车发送消息按钮
        sent_AD_xpath: str = (
            "x://textarea/../../../../../../../../../../div[1]//textarea"  # 输入文字的xpath
        )
        tab_messages.get(AD_url, timeout=65)
        # time.sleep(45)

        if base_one.cash_vs_in_comings():
            print("现金大于进货合同金额，发布广告")
            tab_messages.ele('x://a[@href="/messages/"]').click()

        else:
            print("现金小于进货合同金额，不发布广告")
            return

        if (
            len(tab_messages.eles('x://div[text()="reconnecting ..."]')) > 0
        ):  # 判断聊天窗口超时提醒文字
            raise MessageError("聊天窗口超时")
        elif (
            len(tab_messages.eles('x://a[@href="/messages/chatroom_Sales/"]')) == 0
        ):  # 判断频道加载成功
            raise MessageError("聊天窗口超时")

        # 点击英文交易频道
        tab_messages.ele(messages_EN).click()
        print("点击英文交易频道{}".format(time.ctime()))
        logging.info("点击英文交易频道")
        time.sleep(1)
        # 输入广告文字
        time.sleep(1)
        tab_messages.ele(sent_AD_xpath).input(vals=AD_str, clear=True)
        time.sleep(1)
        # 点击回车发送广告
        tab_messages.ele(enter_xpath).click()
        logging.info("点击回车发送广告")
        time.sleep(10)
        # 点击中文交易频道
        print("点击中文交易频道{}".format(time.ctime()))
        logging.info("点击中文交易频道")
        time.sleep(1)
        tab_messages.ele(messages_ZH).click()
        # 输入广告文字
        time.sleep(1)
        tab_messages.ele(sent_AD_xpath).input(vals=AD_str_ZH, clear=True)
        time.sleep(1)
        # 点击回车发送广告
        time.sleep(1)
        tab_messages.ele(enter_xpath).click()
        logging.info("点击回车发送广告")

    def executives_train(self, position: int = None, num: int = None):
        """高管培训\n
        position参数1:高管从上至下的位置,索引从0开始\n
        num参数2:高管需要培训的技能顺序,索引从0开始。"""
        tab_executives.get("https://www.simcompanies.com/headquarters/executives/",timeout=65)
        executives_position: list = []  # 高管列表
        train_position: list = []  # 培训列表
        new_var = tab_executives.eles('x://label[text()="Board room"]/../div/*/div')
        new_var2 = tab_executives.eles(
            'x://label[text()="Staff"]/..//div[@class="row"]/div/div'
        )
        executives_position += new_var + new_var2


base_one = BaseMain()


def Retail_sales_main(base1: BaseMain) -> None:
    """零售主函数，用于启动线程
    参数1:实例化的主基地类
    """
    count0 = 0
    while count0 < 2000:
        count0 += 1
        print("第{}次运行base1.retail()  {}".format(count0, time.ctime()))
        logging.info("第{}次运行base1.retail() ".format(count0))
        base1.retail(name="Diesel")
        base1.retail(name="Petrol")


def AD_main(base1: BaseMain) -> None:
    """广告主函数，用于启动线程\n
    参数1:实例化的主基地类
    """
    count = 0
    while 1:
        # randint_1 = random.randint(61, 81)  # 设置间隔时间区间
        # base1.place_AD()
        # print("距离下次发布广告随机间隔为{}分钟   {}".format(randint_1, time.ctime()))
        # logging.info("距离下次发布广告随机间隔为{}分钟  ".format(randint_1))
        # time.sleep(60 * randint_1)  # 间隔随机分钟发送一次
        count += 1
        # randint_1 = random.randint(61, 81)  # 设置间隔时间区间
        randint_1 = 61  # 设置间隔时间区间

        try:

            base1.place_AD()
            print("距离下次发布广告间隔为{}分钟   {}".format(randint_1, time.ctime()))
            logging.info("距离下次发布广告随机间隔为{}分钟  ".format(randint_1))
            time.sleep(60 * randint_1)  # 间隔随机分钟发送一次
        except MessageError as e:
            print(e)
            logging.error(e)
            continue
        finally:
            logging.info(f"运行广告发送第{count}次完成，不论成败")


def Executives_Train_main(base1: BaseMain, position: int = None, num: int = None):
    """高管培训主函数，用于启动线程。\n
    base1参数1:基类实例
    position参数2:高管从上至下的位置,索引从0开始\n
    num参数3:高管需要培训的技能顺序,索引从0开始。
    """
    base1.executives_train(position, num)


def main():
    """程序启动主函数"""
    with ThreadPoolExecutor(max_workers=3) as tpool:
        futures: list = [
            tpool.submit(Retail_sales_main, base_one),
            tpool.submit(AD_main, base_one),  # 发布广告，注释即可关闭
            tpool.submit(
                Executives_Train_main, base_one, position=7, num=2
            ),  # 高管培训，注释即可关闭
        ]

    wait(futures, return_when=FIRST_EXCEPTION)


if __name__ == "__main__":
    main()
